const Koa = require('koa');
const path = require('path');
const logger = require('./util/logger');
const { isProd } = require('./util');
const appConfig = require('./config');
const uuid = require('node-uuid');

class Application extends Koa {
  
  constructor(options = {}) {
    super();
    this.uuid = uuid.v1();
    options.static && this.useStatic(options.static);
    options.favicon && this.useFavicon(options.favicon.path, options.favicon);
    options.webpack && this.useWebpack(options.webpack);
    options.session && this.useSession(options.session);
    options.body && this.useBodyParser(options.body);
    options.auth && this.useAuth(options.auth);
    options.routePath && this.useRoutes(options.routePath);
    options.proxy && this.useProxy(options.proxy);
    options.spa && this.useSpa(options.spa);
    options.socket && this.useSocket(options.socket);
  }
  
  useStatic(opt) {
    if (!opt.path && appConfig.root) {
      opt.path = appConfig.root + (isProd() ? '/static/dist' : '/static');
    } else if (!opt.path) {
      throw new Error('Need static path');
    }
    this.use(require('koa-static')(opt.path, opt));
    return this;
  }
  
  useWebpack(configPath, force) {
    if (!isProd() || force) {
      const WebpackMiddleware = require('./middleware/webpack');
      const config = require(path.join(appConfig.root, configPath));
      const middleware = new WebpackMiddleware(config);
      this.use(middleware.devMiddleware({ noInfo: true, publicPath: config.output.publicPath }));
      this.use(middleware.hotMiddleware());
    }
    return this;
  }
  
  useSession(opt) {
    if (!this.session) {
      this.session = require('./middleware/session')(opt);
    }
    this.use(this.session);
    return this;
  }
  
  useFavicon(opt = {}) {
    if (!opt.path && appConfig.root) {
      opt.path = appConfig.root + (isProd() ? '/static/dist' : '/static') + '/images/favicon.icon';
    } else if (!opt.path) {
      throw new Error('Need favicon path.');
    }
    this.use(require('koa-favicon')(opt.path, opt));
    return this;
  }
  
  useBodyParser(opt) {
    this.bodyParserOpt = Object.assign({use: true}, opt);
    return this;
  }
  
  useAuth(opt) {
    this.use(require('./middleware/auth')(opt));
    return this;
  }
  
  useProxy(opt) {
    this.use(require('./middleware/proxy')(opt));
    return this;
  }
  
  useRoutes(dir) {
    if (!dir && appConfig.root) {
      dir = appConfig.root + '/routes';
    } else if (!dir) {
      throw new Error('Need routes path.');
    }
    let router = require('./middleware/route')(dir, this.bodyParserOpt);
    this.use(router.routes());
    this.use(router.allowedMethods());
    return this;
  }
  
  useSpa(options) {
    require('./middleware/spa')(options, this);
    return this;
  }
  
  useSocket(opt) {
    opt = Object.assign({
      pingInterval: 20000,
      pingTimeout: 35000,
      cluster: false
    }, opt);
    let Socket = require('./middleware/socket');
    const io = new Socket(opt);
    io.attach(this);
    if (!this.session) {
      this.session = require('./middleware/session')(opt.session);
    }
    io.session(require('./middleware/socket/middleware/session')(this, this.session));
    return this;
  }
  
  uncaughtException(handler) {
    handler = handler || ((err) => logger.error(err));
    process.on('uncaughtException', handler);
    return this;
  }
  
  start(port = appConfig.server.port) {
    this.listen(port, (err) => {
      if (err) {
        logger.error(err);
        return;
      }
      
      if (!isProd()) {
        require('open')('http://localhost:' + port);
      }
      logger.info('server listening on port: %s', port);
    });
  }
  
}

module.exports = Application;
